#include "Foundation.h"
#include "DateVerify.h"
#include "Hotel.h"
#include "Reservation.h"

#include <map>

/*
====================================
Error messaging
====================================
*/
void Warning(string warning, int wCode) {
	SET_COLOR(YELLOW);

	printf("[WARNING]: %s", warning.c_str());
	if (wCode != ERR_UNDEFINED) {
		printf(" (error code: %i)", wCode);
	}
	printf("\n");

	COLOR_RESTORE();
}

void Error(string error, int eCode) {
	SET_COLOR(RED);

	printf("[ERROR]: %s", error.c_str());
	if (eCode != ERR_UNDEFINED) {
		printf(" (error code: %i)", eCode);
	}
	printf("\n");

	COLOR_RESTORE();
}

void Critical(string critical, int cCode) {
	SET_COLOR(RED);

	printf("[CRITICAL]: %s", critical.c_str());
	if (cCode != ERR_UNDEFINED) {
		printf(" (error code: %i)", cCode);
	}

	printf("\n\n");
	printf("\t####################\n");
	printf("\t## SHUTTING DOWN! ##\n");
	printf("\t####################\n\n\n");
	
	COLOR_RESTORE();

	getchar();
	exit(cCode);
}


/*
====================================
Input Functions
====================================
*/
string ReadString(string prompt, int minLen, int maxLen) {
	if (maxLen < minLen) {
		Warning("ReadString, maxLen < minLen");
		maxLen = minLen+1;
	}

	string ret;
	int len = 0;

	printf("\t%s:  ", prompt.c_str());
	getline(cin, ret);
	len = (int)ret.length();

	// Loop til correct length
	while (len < minLen || len > maxLen) {
		if (len < minLen) {
			printf("\tMust be at least %i characters.\n", minLen);
		} else {
			printf("\tMust be at most %i characters.\n", maxLen);
		}

		printf("\t%s:  ", prompt.c_str());
		getline(cin, ret);
		len = (int)ret.length();
	}
	return ret;
}

char ReadChar(string prompt) {
	string str;

	while (str.length() == 0) {
		str = ReadString(prompt, 0);
	}

	return toupper( str[0] );
}
	
int ReadInt(string prompt, int min, int max) {
	if (max <= min) {
		Warning("ReadInt, max <= min");
		max = min+1;
	}

	int ret = atoi( ReadString(prompt, 1).c_str() );

	while (ret < min || ret > max) {
		if (ret < min) {
			printf("\tMust be at least %i.\n", min);
		} else {
			printf("\tMust be at most %i.\n", max);
		}

		ret = atoi( ReadString(prompt, 1).c_str() );
	}

	return ret;
}

float ReadFloat(string prompt, float min, float max) {
	if (max <= min) {
		Warning("ReadFloat, max <= min");
		max = min+1;
	}

	float ret = atof( ReadString(prompt, 1).c_str() );

	// Loop til correct length
	while (ret < min || ret > max) {
		if (ret < min) {
			printf("\tMust be at least %f.\n", min);
		} else {
			printf("\tMust be at most %f.\n", max);
		}

		ret = atof( ReadString(prompt, 1).c_str() );
	}

	return ret;
}

/*
==================
ReadDate

The return-date is ">= minDate" and "< maxDate".
Empty input returns minDate.
==================
*/
Date ReadDate(string prompt, Date minDate, Date maxDate) {
	Date date = 0;

	do {
		string str = ReadString(prompt + " (DD-MM-YYYY)", 0, 10);
		
		// If correct length, add the date 
		if (str.length() == 10) {
			int day  = atoi( str.c_str()+0 );
			int mon  = atoi( str.c_str()+3 );
			int year = atoi( str.c_str()+6 );
			date = (day) + (mon*100) + (year*10000);
		} else {

			// If not correct length
			if (!str.length()) {

				// If minDate is bigger or the same as current date
				if (minDate >= Singletons::currentDate) {
					date = minDate;
				}
			} else {
				printf("\tAt least 10 characters required.\n");
			}
		}

	// Check if the date is correct
	} while (date < minDate ||
			 date >= maxDate ||
			 DateVerify::Verify(date) != DateVerify::ERR_NONE);

	return date;
}

/*
==================
ReadIntWithOptions

Provide the user with several options, and return the user's choice.

This function call:
	int selection = ReadIntWithOptions(
		"Which fruit is best?", 
		3, "Apple", "Orange", "Fork"
	);
Will print the following to the user:
	Which fruit is best?
		[1]		Apple
		[2]		Orange
		[3]		Fork
	Selection: 
==================
*/
int ReadIntWithOptions(string prompt, int argCount, ...) {
	if (argCount <= 0) {
		Warning("ReadIntWithOptions, bad argument count", argCount);
		return -1;
	}

	// Print the arguments
	printf("\t%s\n", prompt.c_str());

	// Initialize the list
	va_list args;

	// Start up the list
	va_start(args, argCount);

	// Going over and prints all options
	// va_arg gets the next in the list
	for (int i = 0; i < argCount; i++) {
		char *arg = va_arg(args, char*);
		printf("\t\t[%i]:\t%s\n", i+1, arg);
	}

	// Close the list so you can return
	va_end(args);

	// Read the selection
	return ReadInt("Selection", 1, argCount);
}

int ReadIntWithOptions(string prompt, vector<string> args) {
	if (args.size() == 0) {
		Warning("ReadIntWithOptions, bad argument count");
		return -1;
	}

	printf("\t%s\n", prompt.c_str());

	// Going over and print the options
	for (unsigned i = 0; i < args.size(); i++) {
		printf("\t\t[%i]:\t%s\n", i+1, args[i].c_str());
	}

	return ReadInt("Selection", 1, (int)args.size());
}


/*
====================================
Helper Functions
====================================
*/
string capitalize(const string &str) {
	string ret = str;
	
	// Going through and toupper all letters
	for (unsigned i = 0; i < str.length(); i++) {
		ret[i] = toupper(ret[i]);
	}
	
	return ret;
}

void printDate(Date date) {
	printf("%i-%i-%i",
		date%100,		// DD
		(date/100)%100,	// MM
		date/10000		// YYYY
	);
}

string dateToString(Date date) {
	stringstream ss;
	ss << setfill('0') << setw(2) << date % 100 << "-";
	ss << setfill('0') << setw(2) << (date/100)%100 << "-";
	ss << setfill('0') << setw(2) << date/10000;
	
	return ss.str();
}

void confirmDate () {

	// If the current date is different than that of the computer
	if (Singletons::currentDate != Singletons::timer->hent_dato()) {
		string notice = "\nDate has been changed from " + 
						dateToString(Singletons::currentDate) + " to " + 
						dateToString(Singletons::timer->hent_dato());
		Singletons::currentDate = Singletons::timer->hent_dato();
		printf("\n");
		Warning(notice);
	}
}



/*
====================================
Clear list functions
 
The following functions removes all elements in a list, but does
not delete them. This is a workaround of the fact that the List's
destructor deletes the elements.
====================================
*/
void clearListSorted(List *sorted) {
	while (sorted->no_of_elements()) {
		sorted->remove_no(0);
	}
}

void clearListXIFO(List *xifo) {
	while (xifo->no_of_elements()) {
		xifo->remove();
	}
}


/*
==================
Command Map

File-global method pointer for Hotel commands.
This way of interpreting commands on the Hotel object was implemented
as a way of avoiding boilerplate-checks for whether or not the global
Hotel object is instantiated.
 
This violates the 'no STL is required' advice, but overall this makes
it easier and more manageable to handle a lot of commands in a single 
place.
 
'Special' commands such as Select Hotel (H) still requires a separate 
function, however.
 
The string-parameter in the std::map is the command map, and can be
any of the following:
 	"B", "A", "I", "U", "O1", "O2", "E3", etc.
 
Previously, there were one function for each command:
 	void commandFunc() {
		if (!Singletons::hotel) {
			printf("\tNo hotel selected.\n");
		} else {
			Singletons::hotel->commandMethod();
		}
     }
 
 Now, all commands are called as such:
	void callCommand(string cmd) {
	    if (!Singletons::hotel) {
			printf("\tNo hotel selected.\n");
        } else {
            (*Singletons::hotel.*cmds[cmd])();
		}
	}
==================
*/
map<string, void (Hotel::*)(void)> cmds;

void callCommand(string cmd) {

	// If no hotel is "active"
	if (!Singletons::hotel) {
		printf("\tNo hotel selected.\n");
	} else {

		// Check if cmd is a part of cmds %% cmd != NULL
		if (cmds.count(cmd) && cmds[cmd] != NULL) {

			// Update the date and call the correct function
			confirmDate();
			(*Singletons::hotel.*cmds[cmd])();
		} else {
			stringstream ss;
			ss << "Unimplemented / unsupported method for command: " << cmd;
			Error(ss.str());
		}
	}
}

/*
====================================
Menu Functions
====================================
*/
void writeMenu() {
	printf("\nWelcome to SuperHotelManager v2.1\t"); 
	printDate(Singletons::currentDate);
	printf("\n====================================================");
	printf("\n\nRegister:");
	printf("\n\tB - Order/book a room");
	printf("\n\tA - Cancel room reservation");
	printf("\n\tI - Check into room");
	printf("\n\tU - Check out of hotel");
	printf("\n\tR - Register expenses for room");
	printf("\n\tE - Edit menu");
	printf("\n\tO - Overview menu");
	printf("\n\tT - Write all data about the hotel to file");
	printf("\n\tH - Change over to another hotel");
	printf("\n\tQ - Quit");
}

void mainMenu() {
	/* Prepare the commands */
	cmds["B"] = &Hotel::bookRoom;
	cmds["A"] = &Hotel::cancelRoom;
	cmds["I"] = &Hotel::checkIn;
	cmds["U"] = &Hotel::checkOut;
	cmds["R"] = &Hotel::addExpenses;
	cmds["T"] = &Hotel::WriteToFile;
	
	cmds["O1"] = &Hotel::displayInfo;
	cmds["O2"] = &Hotel::displaySuites;
	cmds["O3"] = &Hotel::displayReservationsByPerson;
	cmds["O4"] = &Hotel::whenIsRoomAvailable;
	cmds["O5"] = &Hotel::displayReservationsForRoom;
	cmds["O6"] = &Hotel::displayResidentInRoom;
	cmds["O7"] = &Hotel::displayAvailableRoomsInPeriod;
	
	cmds["E1"] = &Hotel::editDatesBeforeCheckIn;
	cmds["E2"] = &Hotel::editDepartureDateAfterCheckIn;
	cmds["E3"] = &Hotel::changeRoom;
	/* Select a valid hotel first */
	while (!Singletons::hotel)
		selectHotel();
	/* The main loop */
	char command = '?';
	while (command != 'Q') { 
		/* Update time before and after each command */
		confirmDate();

		switch (command) {
			case'B': case'U': case'R': case'I': case'T': case'A':
			{
				stringstream ss;
				ss << command;
				confirmDate();
				callCommand(ss.str());
				break;
			}
				
			case 'H': confirmDate(); selectHotel();				break;
			case 'O': confirmDate(); overviewMenu();			break;
			case 'E': confirmDate(); editMenu();				break;
			default:  writeMenu();
		}
		printf("\n");
		command = ReadChar(Singletons::hotel->getFileName());
	}
	
	/* Write to file at the program termination */
	if (Singletons::hotel) {
		callCommand("T");
	}
}

void editMenu() {
	int sel = ReadIntWithOptions(
		"Edit existing", 4,
		"Change arrival or departure date for guest",
		"Change departure date for checked-in guest",
		"Move customer to another available room",
		"Cancel"
	);
	
	if (sel != 4) {
		stringstream ss;
		ss << "E" << sel;
		callCommand(ss.str());
	}
}

void overviewMenu() {
	int sel = ReadIntWithOptions(
		"Show overview of", 8,
		"The current hotel",
		"Hotel suites",
		"Every reservation by a person",
		"When a room is available",
		"Every reservation of a room",
		"All data available for a resident",
		"Available rooms in a category",
		"Cancel"
	);
	
	if (sel != 8) {
		stringstream ss;
		ss << "O" << sel;
		callCommand(ss.str());
	}
}

void selectHotel() {
	/* Clean up previous hotel */
	if (Singletons::hotel) {
		Singletons::hotel->WriteToFile();
		
		delete Singletons::hotel;
		Singletons::hotel = NULL;
	}
	
	// Open the file "HOTELS.dta"
	ifstream files("HOTELS.dta");

	// Check if open
	if (!files.is_open()) {
		Error("Failed to open file: HOTELS.dta");
		return;
	}

	// Reads number of hotels
	int hotelamont = 0;
	string holder;
	files >> hotelamont;

	if (hotelamont == 1)
		printf("\n%i hotel on file:", hotelamont);
	else
		printf("\n%i hotels on file:", hotelamont);

	// While the file is good and there is a hotel,
	// Go over the names and print them
	while (files.good() && hotelamont) {

		for (int i = 0; i < hotelamont; i++) {

			// Stops at evry 8 printed hotel
			if(i != 0 && i % 8 == 0) {

				printf("\n Enter to get more hotels");
				getline(cin, holder);
			}
				string hold;
				files >> hold;
				files.ignore();

				string fileHotel;
				getline(files, fileHotel);
				printf("\n- %s", fileHotel.c_str());
		}
	}

	// Close the file
	files.close();

	/* Was already open */
	ifstream file("HOTELS.dta");
	int tmp; file >> tmp;

	/* Read hotel name from user */
	string userString = ReadString("\n\nName of hotel");
	
	/* Find the hotel and the corresponding DTA-file */
	while (file.good()) {
		string fileName;
		file >> fileName;
		file.ignore();
		
		string fileHotel;
		getline(file, fileHotel);

		if (capitalize(fileHotel) == capitalize(userString)) {
			printf("Loading hotel %s...\n", fileHotel.c_str());
			
			Singletons::hotel = new Hotel(fileName);
			
			printf("%s loaded!\n", fileHotel.c_str());
			return;
		}
	}
	
	/* The hotel does not exist. */
	string warn = "Could not find hotel '" + userString + "'.";
	Warning(warn);
}





































